【小ネタ】CDKとGitHub Actionsを使ってスタックを簡単に並列デプロイする
はじめに
デプロイ時間を短縮するために、GitHub Actions上でCDKを用いてスタックを並列にデプロイすることを検討したことはありませんか。 CircleCIではコマンド定義が可能ですが、GitHub Actions(以降GHAとする)の場合、スタック分ジョブを定義するか、独自のAction定義をする方法があります。今回は、簡単に(=共通化しつつ、記述量の少ない形)スタックを並列にデプロイする方法を紹介します。
※ 注意
CDK自体で並列にスタックをデプロイするオプションについては、いくつか進行中のPRが存在します。本体に取り込まれた場合この方法は必要なくなることが予想されます。
GitHub Actionsの設定
方針
設定の肝は、cdk list
とGHAのstrategyのmatrix
を活用します。実行結果のイメージは以下のようになります。
設定内容
例えばLambdaとAPIGatewayを使ったサーバーレスAPIをデプロイする際に、Lambdaだけ並列にデプロイしたい場合は、以下のようにします。設定のポイントは後述します。
name: Deploy env: PROJECT_NAME: projectName on: push: branches: - develop - master jobs: setup: name: Setup runs-on: ubuntu-latest outputs: stageName: ${{ steps.setenv.outputs.stageName }} stacks: ${{ steps.setenv.outputs.stacks }} steps: - name: Checkout uses: actions/checkout@v2 - name: Use Node uses: actions/setup-node@v1 with: node-version: "16.x" - name: Cache node modules uses: actions/cache@v2 env: cache-name: cache-node-modules with: path: "**/node_modules" key: ${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('**/yarn.lock') }} - name: Install run: yarn install --frozen-lockfile - name: Set environment id: setenv run: | stageName="" if ${{ github.ref == 'refs/heads/develop' }}; then stageName=dev elif ${{ github.ref == 'refs/heads/master' }}; then stageName=prd else exit 1 fi echo "::set-output name=stageName::$stageName" api_lambda_stacks=$(yarn --silent \ cdk list --context stageName=$stageName *-api-lambda-* \ --long --json| \ jq -c 'map(.id)') echo "::set-output name=stacks::$(echo $api_lambda_stacks | jq -c)" deploy-api-lambda: name: deploy-api-lambda needs: - setup runs-on: ubuntu-latest timeout-minutes: 10 strategy: fail-fast: false matrix: stack: ${{fromJson(needs.setup.outputs.stacks)}} permissions: id-token: write contents: read steps: - name: Checkout uses: actions/checkout@v2 - name: Use Node uses: actions/setup-node@v2 with: node-version: '16.x' - name: Restore node modules uses: actions/cache@v2 env: cache-name: cache-node-modules with: path: "**/node_modules" key: ${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('**/yarn.lock') }} - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@v1 with: role-to-assume: ロール名 aws-region: リージョン名 - name: Deploy shell: bash run: | yarn deploy -c stageName=${{ needs.setup.outputs.stageName }} \ ${{ matrix.stack }} \ --require-approval never
設定のポイント
setup
というジョブで、デプロイするスタック名のリストを動的に生成し、outputの変数に設定します。後述しますが、本変数をstrategyのmatrixに指定することになります。
# zsh yarn --silent cdk list --context stageName=dev \*-api-lambda-\* --long --json| jq -c 'map(.id)' ["dev-a-api-lambda-article","dev-b-api-lambda-auth","dev-c-api-lambda-doc","dev-d-api-lambda-git-hub-app","dev-e-api-lambda-user"]
コマンドオプションの補足
- yarnの
--silent
オプションは標準出力にyarnの出力が含まれないようにするため - cdkの
--long
--json
オプションはlist結果をjqでパースしやすくするため
前述のoutputの変数をstrategyのmatrixに指定して並列にデプロイすることが実現できます。
deploy-api-lambda: name: deploy-api-lambda ... strategy: fail-fast: false matrix: stack: ${{fromJson(needs.setup.outputs.stacks)}} ... - name: Deploy shell: bash run: | yarn deploy -c stageName=${{ needs.setup.outputs.STAGE_NAME }} \ ${{ matrix.stack }} \ --require-approval never
Lambdaがデプロイされたあと、APIGatewayをデプロイしたい場合は、以下のコードで実行順の制御が可能です。
deploy-api: name: deploy-api needs: - setup - deploy-api-lambda: steps: # APIスタックのデプロイロジックを書く
最後に
CircleCIのコマンド定義が欲しくなったら基本的にはstrategyのmatrixで代用可能か考えてみると良さそうです。 またNodejsFunctionだと、各スタックデプロイ時に全てソースに対してビルドが走ってしまうため、クレジット消費が無駄になってしまう問題があります。事前にビルド、キャッシュし、NodejsFunctionは利用しないことで防ぐことが可能です。esbuildは高速なので、気にならない程度だとは思いますがご注意ください。